/**
 * @file ESP8266WSYBoot.cpp
 * @author BH3PXN
 * @brief 本程序修改自BG1REN的源码
 *        https://github.com/qiwenmin/esp8266-ir-gateway-bemfa
 * @version 0.1
 * @date 2022-03-23
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#pragma once

#include "DebugLog.h"
#include "ESP8266WSYBoot.h"
#include <Arduino.h>
#include <AsyncMqttClient.h>
#include <ESP8266WiFi.h>
#include <FS.h>
#include <LittleFS.h>
#include <Ticker.h>
#include <functional>
#include <list>
#include <map>

class BemfaMqtt {
public:
    typedef std::function<void(const String &topic, const String &msg, AsyncMqttClient &mqttClient)> MessageListener;

    BemfaMqtt(){};
    void disconnect() {
        _reconnect_ticker.detach();
        _mqtt_client.disconnect(true);
        _mqtt_client.clearQueue();
    };

    void reconnect() {
        _connect();
    };

    void setBemfaMqtt(const String &host, int port, const String &client_id) {
        _host = host;
        _port = port;
        _client_id = client_id;
        //_mqtt_client.setServer(_host.c_str(), _port);
        //_mqtt_client.setClientId(_client_id.c_str());
    }

    void onMessage(const String &topic, MessageListener listener) {
        _mlsm[topic].push_back(listener);
    };

    void sendMessage(const String &topic, const String &msg) {
        _mqtt_client.publish(topic.c_str(), 0, false, msg.c_str(), msg.length());
    };

    void clearTopic() {
        for (auto it = _mlsm.begin(); it != _mlsm.end(); ++it) {
            DEBUG_LOG_LN(it->first);
            _mlsm.erase(it);
        }
    }
    String getBemfaToken() {
        String data = "";
        File file = LittleFS.open("/bemfa.txt", "r");
        if (!file) {
            DEBUG_LOG_LN("[MQTT] LittleFS Failed to open file for reading /bemfa.txt");
            return "";
        }
        while (file.available()) {
            char c = (char)file.read();
            data = data + c;
        }
        file.close();
        DEBUG_LOG("[MQTT] bemfa.txt:>");
        DEBUG_LOG(data);
        DEBUG_LOG_LN("<");
        return data;
    }

    bool saveBemfaToken(String token) {
        bool res = false;
        File file = LittleFS.open("/bemfa.txt", "w");
        if (!file) {
            DEBUG_LOG_LN("[MQTT] LittleFS Failed to open file for writing");
            return res;
        }

        if (file.print(token.c_str())) {
            res = true;
            DEBUG_LOG_LN("[MQTT] LittleFS File written");
        } else {
            DEBUG_LOG_LN("[MQTT] LittleFS Write failed");
        }
        // delay(2000); // Make sure the CREATE and LASTWRITE times are different

        file.close();
        return res;
    }

    void begin() {

        _mqtt_client.setServer(_host.c_str(), _port);
        _mqtt_client.setClientId(_client_id.c_str());
        // Mqtt connection events

        _mqtt_client.onConnect([this](bool sessionPresent) {
            DEBUG_LOG_LN("[MQTT] Connected to MQTT.");
            DEBUG_LOG("[MQTT] Session present: ");
            DEBUG_LOG_LN(sessionPresent);

            for (auto it = _mlsm.begin(); it != _mlsm.end(); ++it) {
                uint16_t packetIdSub = _mqtt_client.subscribe(it->first.c_str(), 0);
                DEBUG_LOG("[MQTT] Subscribing <");
                DEBUG_LOG(it->first);
                DEBUG_LOG("> at QoS 0, packetId: ");
                DEBUG_LOG_LN(packetIdSub);
                packetIdSub = 0;
            }
        });

        _mqtt_client.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
            DEBUG_LOG_LN("[MQTT] Disconnected from MQTT.");

            if (WiFi.isConnected()) {
                DEBUG_LOG_LN("[MQTT] Reconnect after 5 seconds...");
                _reconnect_ticker.once(5, [this]() {
                    DEBUG_LOG_LN("[MQTT] Reconnecting...");
                    _connect();
                });
            }
        });

        // Mqtt subscribe events
        _mqtt_client.onSubscribe([this](uint16_t packetId, uint8_t qos) {
            DEBUG_LOG_LN("[MQTT] Subscribe acknowledged:");
            DEBUG_LOG("         packetId: ");
            DEBUG_LOG_LN(packetId);
            DEBUG_LOG("              qos: ");
            DEBUG_LOG_LN(qos);
        });

        _mqtt_client.onUnsubscribe([this](uint16_t packetId) {
            DEBUG_LOG_LN("[MQTT] Unsubscribe acknowledged:");
            DEBUG_LOG("         packetId: ");
            DEBUG_LOG_LN(packetId);
        });

        // Mqtt pub/msg events
        _mqtt_client.onPublish([this](uint16_t packetId) {
            DEBUG_LOG_LN("[MQTT] Publish acknowledged:");
            DEBUG_LOG("         packetId: ");
            DEBUG_LOG_LN(packetId);
        });

        _mqtt_client.onMessage([this](char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
            auto msg = String("");
            for (size_t i = 0; i < len; i++) {
                msg += payload[i];
            }

            DEBUG_LOG_LN("[MQTT] Message received:");
            DEBUG_LOG("          topic: ");
            DEBUG_LOG_LN(topic);
            DEBUG_LOG("            qos: ");
            DEBUG_LOG_LN(properties.qos);
            DEBUG_LOG("            dup: ");
            DEBUG_LOG_LN(properties.dup);
            DEBUG_LOG("         retain: ");
            DEBUG_LOG_LN(properties.retain);
            DEBUG_LOG("            len: ");
            DEBUG_LOG_LN(len);
            DEBUG_LOG("          index: ");
            DEBUG_LOG_LN(index);
            DEBUG_LOG("          total: ");
            DEBUG_LOG_LN(total);
            DEBUG_LOG("        payload: ");
            DEBUG_LOG_LN(msg);

            if (_mlsm.count(topic) == 1) {
                auto lst = _mlsm.at(topic);
                for (auto it = lst.begin(); it != lst.end(); ++it) {
                    (*it)(topic, msg, _mqtt_client);
                }
            }
        });

        // WiFi events
        _got_ip_handler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP &) {
            DEBUG_LOG_LN("[MQTT] Connected to WiFi.");
            _connect();
        });

        _disconnected_handler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected &) {
            DEBUG_LOG_LN("[MQTT] Disconnected from WiFi.");
            _reconnect_ticker.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
        });
    };

    void loop(){};

private:
    void _connect() {
        if (_mqtt_client.connected()) {
            DEBUG_LOG("[MQTT] Connected,Ignore this request");
            return;
        }
        DEBUG_LOG("[MQTT] Connecting to MQTT server: ");
        DEBUG_LOG(_host);
        DEBUG_LOG(":");
        DEBUG_LOG(_port);
        DEBUG_LOG(" @ ");
        DEBUG_LOG(_client_id);
        DEBUG_LOG_LN();

        _mqtt_client.connect();
    };

private:
    String _host;
    int _port;
    String _client_id;

    typedef std::list<MessageListener> MessageListenerList;
    typedef std::map<String, MessageListenerList> MessageListenersMap;

    MessageListenersMap _mlsm;
    AsyncMqttClient _mqtt_client;

    WiFiEventHandler _got_ip_handler;
    WiFiEventHandler _disconnected_handler;

    Ticker _reconnect_ticker;

    uint32_t connectTimeout;
};